#include "daemon.h"

#include <QString>
#include <QNetworkAccessManager>

#include "config.h"
#include "disk.h"
#include "sender.h"
#include "receiver.h"
#include "action.h"
#include "tuboxfile.h"
#include "communicator.h"
#include "uicommunicator.h"

#include <base64/base64.h>

#include <iostream> //debug
using std::cout;
using std::cerr;
using std::endl;

/**
 * Helper function
 */
#define connectDaemon(type, ...) \
	connect(receiver, SIGNAL(sig##type(unsigned int,## __VA_ARGS__)), this, SLOT(slot##type(unsigned int,## __VA_ARGS__)))

namespace Tubox{

Daemon::Daemon(const QString& configFileName) throw(DaemonError): configFileName(configFileName), isConnected(false) {
	config = new Config(configFileName);
	try {
		config->load();
	}
	catch(XmlError& e) {
		throw(DaemonError("Daemon constructor failed trying to load config",0));
	}
	disk = new Disk(config->getDisk(), this);
	sender = new Sender(config->getServerUrl(), config->getPort());
	receiver = new Receiver();

	timer = new QTimer();
	connect(timer, SIGNAL(timeout()), this, SLOT(updateDisk()));
	timer->start(1000 * 36);

	uiCommunicator = new UICommunicator(this);

	connect(sender, SIGNAL(sigReply(QNetworkReply*)), receiver, SLOT(receive(QNetworkReply*)));
	
	connectDaemon(Login);
	connectDaemon(Logoff);
	connectDaemon(Download, File&);
	connectDaemon(Upload, File&);
	connectDaemon(List, const QString&, QList<File>, QList<Folder>);
	connectDaemon(Move, const QString&, const QString&);
	connectDaemon(Delete, QString&);
	connectDaemon(Mkdir, const Folder&);
	connectDaemon(Copy, const QString&, const File&);
	connectDaemon(Version, const File&);
}

Daemon::~Daemon() {
	if(config) delete config;
	if(disk) delete disk;
	if(sender) delete sender;
	if(receiver) delete receiver;

}

/**
 * todo: login
 * user: thiago
 * password: 12345
 */

bool Daemon::askLogin() {
	const QString user = config->getUser();
	const QString password = config->getPassword();
	if(user.isEmpty()) return false;
	QString request;
	request += "todo=login&user=";
	request += user;
	request += "&password=";
	request += password;

	sender->send(request);
	return true;
}

/**
 * todo: logoff
 */

bool Daemon::askLogoff() {
	if(!isConnected) return false;
	QString request;
	request += "todo=logoff";

	sender->send(request);
	return true;
}

/**
 * todo: download
 * path: /documentos
 * name: texto
 */

bool Daemon::askDownload(const QString& path, const QString& name) {
	if(!isConnected or name.isEmpty()) return false;
	QString request;
	request += "todo=download";
	request += "&path=";
	request += path;
	request += "&name=";
	request += name;

	sender->send(request);
	return true;
}

/**
 * todo: upload
 * path: /documentos
 * name: texto
 * version: 4
 * file:TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
 */


bool Daemon::askUpload(const QString& path, const QString& name,
											 QByteArray& file, const unsigned int version) {
	if(!isConnected or name.isEmpty()) return false;
	QString request;
	request += "todo=upload";
	request += "&path=";
	request += path;
	request += "&name=";
	request += name;
	request += "&version=";
	request += QString::number(version);
	request += "&file=";
	QFile qfile(disk->getAbsolutePath() + "/" + path + "/" + name);
	QString fileBase64WebSafe;
	if(qfile.open(QIODevice::ReadOnly)) {
		QByteArray data = qfile.readAll();
		fileBase64WebSafe = QString::fromStdString(modp::b64w_encode(string(data.data())));
//		cout << "Dados: " << data.data() << endl;
		qfile.close();
	}
	else
		return false;
//	cout << qfile.size() << endl;
//	cout << fileBase64WebSafe.size() << endl;
	if(fileBase64WebSafe.isEmpty() && !file.isEmpty()) {
		return false;
	}
	else {
		request = request + fileBase64WebSafe;
		sender->send(request);
		return true;
	}
}

/**
 * todo: list
 * path: /documentos
 *
 * todo: list
 * path: /videos
 */

bool Daemon::askList(const QString& path) {
	if(!isConnected) return false;
	QString request;
	request += "todo=list";
	request += "&path=";
	request += path;

	sender->send(request);
	return true;
}

/**
 * todo: move
 * oldPath: /videos
 * oldName: video
 * newPath: /videos/tutoriais
 * newName: videoSD
 */

bool Daemon::askMove(const QString& oldPath, const QString& oldName,
										 const QString& newPath, const QString& newName) {
	if(!isConnected or oldName.isEmpty() or newName.isEmpty()) return false;
	QString request;
	request += "todo=move";
	request += "&oldPath=";
	request += oldPath;
	request += "&oldName=";
	request += oldName;
	request += "&newPath=";
	request += newPath;
	request += "&newName=";
	request += newName;

	sender->send(request);
	return true;
}

/**
 * todo: delete
 * path: /documentos
 * name: texto
 */

bool Daemon::askDelete(const QString& path, const QString& name) {
	if(!isConnected or name.isEmpty()) return false;
	QString request;
	request += "todo=delete";
	request += "&path=";
	request += path;
	request += "&name=";
	request += name;

	sender->send(request);
	return true;
}

/**
 * todo: mkdir
 * path: /
 * name: musicas
 */

bool Daemon::askMkdir(const QString& path, const QString& name) {
	if(!isConnected) return false;
	QString request;
	request += "todo=mkdir";
	request += "&path=";
	request += path;
	request += "&name=";
	request += name;

	sender->send(request);
	return true;
}

/**
 * todo: copy
 * path: /documentos
 * name: texto
 * copyPath: /documentos/importantes
 */

bool Daemon::askCopy(const QString& path, const QString& name,
										 const QString& copyPath) {
	if(!isConnected) return false;
	QString request;
	request += "todo=copiar";
	request += "&path=";
	request += path;
	request += "&name=";
	request += name;
	request += "&copyPath=";
	request += copyPath;

	sender->send(request);
	return true;
}

/**
 * todo: version
 * path: /videos
 * name: video
 *
 * todo: version
 * path: /
 */

bool Daemon::askVersion(const QString& path, const QString& name) {
	if(!isConnected) return false;
	QString request;
	request += "todo=version";
	request += "&path=";
	request += path;
	if(!name.isEmpty()) {
		request += "&name=";
		request += name;
	}

	sender->send(request);
	return true;
}

void Daemon::slotLogin(unsigned int status) {
	try {
		switch(status) {
			case 0:{
				isConnected = true;
				cout << "Tubox connected." << endl;
				updateDisk();
				break;
			}
			case 1:
				throw DaemonError("Login error. Incorrect user or password.", status);
				break;
			case 2:
				throw DaemonError("Login error. User already logged in.", status);
				break;
			default:
				throw DaemonError("Login unknown error.", status);
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << "slotLogin() error. " << disk->getAbsolutePath().toStdString() << " cannot be checked. " << endl;
	}
}
void Daemon::slotLogoff(unsigned int status) {
	try {
		switch(status) {
			case 0:
				isConnected = false;
				cout << "Tubox disconnected." << endl;
				break;
			case 1:
				throw DaemonError("Logoff error. You are not connected.", status);
				break;
			case 2:
				throw DaemonError("Logoff unknown error.", status);
				break;
			default:
				throw DaemonError("Logoff exception.", status);
				break;
			}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
}

void Daemon::slotDownload(unsigned int status, File& file) {
	try {
		switch(status) {
			case 0: {
				disk->update(file);
				cout << file.getName().toStdString() << " downloaded sucessfully." << endl;
				Communicator comm;
				comm.fileDownloaded(file);
				break;
			}
			case 1:
				throw DaemonError("Download error. You are not connected.", status);
				break;
			case 2:
				throw DaemonError(string("Download error.")
													+ file.getName().toStdString()
													+ string(" does not exist."), status);
				break;
			case 3:
				throw DaemonError("Download error. Unknown server error.", status);
				break;
			default:
				throw DaemonError("Download exception", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << e.getFile().getFullPath().toStdString() << " was not updated." << endl;
	}
	catch(CommunicatorError& e) {
		cerr << e.what() << endl;
		if(!e.getMessage().isEmpty())
			cerr << e.getMessage().toStdString() << " was not delivered." << endl;
	}
}

void Daemon::slotUpload(unsigned int status, File& file) {
	try {
		switch(status) {
			case 0: {
				disk->update(file, false);
				cout << file.getName().toStdString() << " updated successfully" << endl;
				Communicator comm;
				comm.fileUploaded(file);
				break;
			}
			case 1:
				throw DaemonError(string("Update error. Cannot update ")
													+ file.getName().toStdString()
													+ string(". Are you authenticated?"), status);
				break;
			case 2:
				throw DaemonError(string("Update error. Cannot update ")
													+ file.getName().toStdString()
													+ string(". Higher version at server."), status);
				break;
			case 3:
				throw DaemonError(string("Update error. Cannot update ")
													+ file.getName().toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("Update exception", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << e.getFile().getFullPath().toStdString() << " was not updated." << endl;
	}
	catch(CommunicatorError& e) {
		cerr << e.what() << endl;
		if(!e.getMessage().isEmpty())
			cerr << e.getMessage().toStdString() << " was not delivered." << endl;
	}
}
void Daemon::slotList(unsigned int status, const QString& path, QList<File> files, QList<Folder> folders) {
	unsigned int countFail = 0;
	try {
		switch(status) {
			case 0: {
				QString nonConstPath = path;
				Folder f(nonConstPath);
				TuboxFile tbFile(disk->getAbsolutePath(), f.getPath());
				QList<Action*> actions = tbFile.check();
				actions += tbFile.update(files, folders);
				foreach(Action* a, actions) {
					countFail += !a->doIt(this); //doIt returns a bool.
				}
				if(countFail) {
					cerr << "slotList() found " << countFail  << " errors." << endl;
				}
				// FIXME
				while (!actions.isEmpty()){
					delete actions.takeFirst();
				}
				break;
			}
			case 1:
				throw DaemonError(string("List error. Cannot list ")
													+ path.toStdString()
													+ string(". Are you authenticated?"), status);
				break;
			default:
				throw DaemonError("List error.", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << e.getFile().getFullPath().toStdString() << " was not updated." << endl;
	}
}

void Daemon::slotDelete(unsigned int status, QString& fullpath) {
	try {
		switch(status) {
			case 0: {
				QFileInfo qfileinfo(disk->getAbsolutePath() + "/" + fullpath);
				if(qfileinfo.isFile())
					disk->remove(File(fullpath));
				else
					disk->remove(Folder(fullpath));
				cout << fullpath.toStdString() << " deleted successfully" << endl;
				break;
			}
			case 1:
				throw DaemonError(string("Delete error. Cannot delete ")
													+ fullpath.toStdString()
													+ string(". Are you authenticated?."), status);
				break;
			case 2: {
				QFileInfo qfileinfo(disk->getAbsolutePath() + "/" + fullpath);
				if(qfileinfo.isFile())
					disk->remove(File(fullpath));
				else
					disk->remove(Folder(fullpath));
				throw DaemonError(string("Delete error. Cannot delete ")
													+ fullpath.toStdString()
													+ string(". Server unknown error."), status);
				break;
			}
			default:
				throw DaemonError("Delete exception", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << e.getFile().getFullPath().toStdString() << " was not updated." << endl;
	}
}



void Daemon::slotVersion(unsigned int status, const File& file) {
	cout << "slotVersion() - status " << status << endl;
}

void Daemon::slotCopy(unsigned int status, QString path, const File& file) {
		try {
		switch(status) {
			case 0: {
				cout << "File " << file.getFullPath().toStdString() << "created sucessfully." << endl;
				Communicator comm;
				comm.fileCreated(file);
				break;
			}
			case 1:
				throw DaemonError(string("Copy error. Cannot copy ")
													+ file.getName().toStdString() + string(" to ")
													+ path.toStdString()
													+ string(". Are you authenticated?."), status);
				break;
			case 2:
				throw DaemonError(string("Copy error. Cannot copy ")
													+ file.getName().toStdString() + string(" to ")
													+ path.toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("Copy exception", status);
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(CommunicatorError& e) {
		cerr << e.what() << endl;
		if(!e.getMessage().isEmpty())
			cerr << e.getMessage().toStdString() << " was not delivered." << endl;
	}
}

void Daemon::slotMkdir(unsigned int status, const Folder& folder) {
	try {
		switch(status) {
			case 0: {
				cout << "Directory " << folder.getFullPath().toStdString() << "created sucessfully." << endl;
				Communicator comm;
				comm.folderCreated(folder);
				break;
			}
			case 1:
				throw DaemonError("Mkdir error. Are you authenticated?", status);
			break;
			case 2:
				throw DaemonError("Mkdir error. Server unknown error.", status);
			break;
			default:
				throw DaemonError("Mkdir exception", status);
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(CommunicatorError& e) {
		cerr << e.what() << endl;
		if(!e.getMessage().isEmpty())
			cerr << e.getMessage().toStdString() << " was not delivered." << endl;
	}
}

void Daemon::slotMove(unsigned int status, const QString& oldPath, const QString& newPath) {
	try {
		switch(status) {
			case 0:
				disk->move(oldPath, newPath);
				cout << oldPath.toStdString() << " moved to " << newPath.toStdString() <<" successfully." << endl;
				break;
			case 1:
				throw DaemonError(string("Move error. Cannot move ")
													+ oldPath.toStdString()
													+ string(" to ")
													+ newPath.toStdString()
													+ string(". Are you authenticated?"), status);
				break;
			case 2:
				throw DaemonError(string("Move error. Cannot move ")
													+ oldPath.toStdString()
													+ string(" to ")
													+ newPath.toStdString()
													+ string(". Server unknown error."), status);
				break;
			default:
				throw DaemonError("Move exception.", status);
				break;
		}
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << e.getFile().getFullPath().toStdString() << " was not updated." << endl;
	}
}

void Daemon::updateDisk() {
	cout << "Updating Disk" << endl;
	try {
		if(!askList("/"))
			askLogin();
	}
	catch(DaemonError& e) {
		cerr << e.what() << endl;
		cerr << "Status returned: " << e.getStatus() << endl;
	}
	catch(FileSystemError& e) {
		cerr << e.what() << endl;
		cerr << "updateDisk() error. " << disk->getAbsolutePath().toStdString() << " cannot be checked. " << endl;
	}
	catch(CommunicatorError& e) {
		cerr << e.what() << endl;
		if(!e.getMessage().isEmpty())
			cerr << e.getMessage().toStdString() << " was not delivered." << endl;
	}
}

}
